package fr.umlv.galcon.ship;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.io.IOException;

import fr.umlv.galcon.item.ItemListSingleton;
import fr.umlv.galcon.planet.Planet;
import fr.umlv.galcon.utils.Utils;

/**
 * This class manages the moves of a Ship.
 * 
 **/
public class ShipMovement {
    private final ShipAbstract ship;
    private final Squadron squadron;
    private final ShipAbstract shipProperties;

    private final Point2D center;
    private double[] trajectoryEquation;
    private Point2D lastPosition;

    /**
     * Constructor
     * 
     * @param ship
     *            The Ship to manage the moves.
     * @param center
     *            The position of the Ship.
     */
    public ShipMovement(ShipAbstract ship, Point2D center) {
	this.ship = ship;
	this.squadron = ship.getSquadron();
	this.shipProperties = ship;
	this.center = center;
	this.trajectoryEquation = null;
	this.lastPosition = null;
    }

    /**
     * Get the direction the ship has to follow to avoid a Planet.
     * 
     * @param planetImpacted
     *            The Planet which will be impacted.
     * @return
     */
    private boolean chooseDirection(Planet planetImpacted, Point2D p) {

	double shipX, shipY, planetX, planetY;

	shipX = this.center.getX() - planetImpacted.getCenter().getX();
	shipY = this.center.getY() - planetImpacted.getCenter().getY();
	planetX = this.squadron.getDestinationPlanet().getCenter().getX() - planetImpacted.getCenter().getX();
	planetY = this.squadron.getDestinationPlanet().getCenter().getY() - planetImpacted.getCenter().getY();

	if ((shipX * planetY) > (shipY * planetX))
	    return false;
	else if ((shipX * planetY) < (shipY * planetX) || ((shipX * planetX) < 0.0) || ((shipY * planetY) < 0.0))
	    return true;
	else if ((shipX * shipX + shipY * shipY) < (planetX * planetX + planetY * planetY))
	    return false;

	return false;
    }

    /**
     * Get the last position of the ship
     * 
     * @return last position
     */
    public Point2D getLastPosition() {
	return lastPosition;
    }

    /**
     * Function which if the ship impacts the destination planet and destroy it
     * if necessary yes.
     * 
     * @param g
     *            Graphics2D object where the Item is drawn.
     */
    public void impactDestination(Graphics2D g) {
	int radius = 5;
	int diameter = radius * 2;
	Planet destinationPlanet = this.squadron.getDestinationPlanet();

	// If the ship impacts a planet
	if (destinationPlanet.contains(this.center)) {

	    this.ship.setRemoved(true);

	    // If the impacted planet has not the same owner as the squadron or
	    // if doesn't produce the same type of ships.
	    if (this.squadron.getOwner().getId() != destinationPlanet.getOwner().getId()
		    || this.shipProperties.getShipProperties() != destinationPlanet.getShipProperties()) {
		// Draw the impact
		try {
		    g.drawImage(Utils.getExplosionFile(), (int) this.center.getX() - 10, (int) this.center.getY() - 10, 20, 20,
			    null);
		} catch (IOException e) {
		    g.setColor(Color.RED);
		    g.fillOval((int) (this.center.getX() - radius), (int) (this.center.getY() - radius), diameter, diameter);
		}

		// Reduce stock of the planet attacked
		destinationPlanet.getStock().reduceStock(this.shipProperties.getShipProperties().getPower());

		// Reduce the real Stock
		int realStock = destinationPlanet.getStock().getRealStock();
		if (realStock > 0 && destinationPlanet.getStock().getShipStock() > 0)
		    destinationPlanet.getStock().setRealStock(realStock - this.shipProperties.getShipProperties().getPower());

		// Look if the owner of the planet has to change
		if (destinationPlanet.getStock().getShipStock() <= 0) {

		    destinationPlanet.conquire(this.squadron.getOwner(), this.shipProperties.getShipProperties());
		}
	    }
	    // It is a friendly move
	    else {
		destinationPlanet.getStock().raiseStock();
	    }
	}
    }

    /**
     * Return the Planet which is impacted
     * 
     * @param point
     *            The position of the Ship.
     * @return The Planet impacted or null
     */
    private Planet impactedPlanet(Point2D point) {
	for (Planet planetImpacted : ItemListSingleton.getInstance().getPlanetList()) {
	    if (planetImpacted.getShape().contains(point) && !this.squadron.getDestinationPlanet().equals(planetImpacted))
		return planetImpacted;
	}
	return null;
    }

    /**
     * Move the current to his next position.
     */
    public void moveTo() {
	// Current position of the Ship
	double centerX = this.center.getX();
	double centerY = this.center.getY();

	// Position of the destination
	double targetX = this.squadron.getDestinationPlanet().getCenter().getX();
	double targetY = this.squadron.getDestinationPlanet().getCenter().getY();

	// Next position of the Ship
	double nextX, nextY;

	// Get the coefficients of the target line
	double a = this.trajectoryEquation[0];
	double b = this.trajectoryEquation[1];

	// Get the speed of the Ship
	int speed = this.shipProperties.getShipProperties().getSpeed();

	if (Double.isInfinite(a)) {
	    if (centerX == targetX) {
		nextX = centerX;
		nextY = (centerY < targetY) ? centerY + speed : centerY - speed;
	    } else {
		nextY = centerY;
		nextX = (centerX < targetX) ? centerX + speed : centerX - speed;
	    }
	} else {
	    if (Math.abs(a) < 1) {
		nextX = (centerX < targetX) ? centerX + speed : centerX - speed;
		nextY = a * nextX + b;
	    } else {
		nextY = (centerY < targetY) ? centerY + speed : centerY - speed;
		nextX = (nextY - b) / a;
	    }
	}

	// Set the new position of the Ship
	// Look if the next movements is an impact
	Point2D nextPoint = new Point((int) nextX, (int) nextY);
	Planet p = impactedPlanet(nextPoint);

	this.lastPosition = new Point((int) this.center.getX(), (int) this.center.getY());

	// Impact
	if (p != null) {
	    rotate(p, nextPoint);
	    setTrajectory();
	}
	// Normal move
	else {
	    this.center.setLocation(nextPoint);
	}
    }

    /**
     * Rotate the Ship around the planet
     * 
     * @param impactedPlanet
     *            The planet to avoid.
     */
    private void rotate(Planet impactedPlanet, Point2D p) {
	double x, y, xx, yy;
	double radius = (impactedPlanet.getShape().getBounds().getWidth() / 2) - 1;
	double departureShipAngle;

	// Center of the planet
	x = impactedPlanet.getCenter().getX();
	y = impactedPlanet.getCenter().getY();

	// Position of the Ship from the impacted planet
	departureShipAngle = SendShip.getAngle(impactedPlanet.getCenter(), this.center);

	double step = (this.shipProperties.getShipProperties().getSpeed()) / 10.0;
	step = step / 3;

	if (chooseDirection(impactedPlanet, p)) {
	    departureShipAngle -= step;
	} else {
	    departureShipAngle += step;
	}

	// The angle has to be in "radian"
	xx = x + (radius) * Math.cos(departureShipAngle);
	yy = y + (radius) * Math.sin(departureShipAngle);

	this.center.setLocation(Math.floor(xx), Math.floor(yy));
    }

    /**
     * Set the Ship Trajectory with an equation : "y = ax + b".
     */
    public void setTrajectory() {
	double targetX = this.squadron.getDestinationPlanet().getCenter().getX();
	double targetY = this.squadron.getDestinationPlanet().getCenter().getY();
	double centerX = this.center.getX();
	double centerY = this.center.getY();

	this.trajectoryEquation = new double[2];

	this.trajectoryEquation[0] = (targetY - centerY) / (targetX - centerX);
	this.trajectoryEquation[1] = centerY - (this.trajectoryEquation[0] * centerX);
    }
}
